package com.zeyu.framework.modules.sys.web;

import com.google.common.collect.Maps;
import com.zeyu.framework.core.common.mapper.JsonMapper;
import com.zeyu.framework.core.security.FormAuthenticationFilter;
import com.zeyu.framework.core.security.SystemAuthorizingRealm;
import com.zeyu.framework.core.security.UsernamePasswordToken;
import com.zeyu.framework.core.security.session.SessionDAO;
import com.zeyu.framework.core.security.utils.SecurityUtils;
import com.zeyu.framework.core.web.BaseController;
import com.zeyu.framework.core.web.Result;
import com.zeyu.framework.core.web.servlet.ValidateCodeServlet;
import com.zeyu.framework.modules.sys.entity.Menu;
import com.zeyu.framework.modules.sys.utils.UserUtils;
import com.zeyu.framework.tools.PasswordGenerate;
import com.zeyu.framework.utils.Collections3;
import com.zeyu.framework.utils.CookieUtils;
import com.zeyu.framework.utils.IdGen;
import com.zeyu.framework.utils.StringUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 登录Controller
 */
@Controller
public class LoginController extends BaseController {

    // ================================================================
    // Constants
    // ================================================================

    // ================================================================
    // Fields
    // ================================================================

    @Autowired
    private SessionDAO sessionDAO;

    // ================================================================
    // Constructors
    // ================================================================

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    // ================================================================
    // Public or Protected Methods
    // ================================================================

    /**
     * 管理登录
     */
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String login(HttpServletResponse response) {
        SystemAuthorizingRealm.Principal principal = SecurityUtils.getPrincipal();

        if (logger.isDebugEnabled()) {
            logger.debug("login, active session size: {}", sessionDAO.getActiveSessions(false).size());
        }

        // 如果已登录，再次访问主页，则退出原账号。
        if (TRUE.equals(NOT_ALLOW_REFRESH_INDEX + "")) {
            CookieUtils.setCookie(response, "LOGINED", "false");
        }

        // 如果已经登录，则跳转到管理首页
        if (principal != null && !principal.isMobileLogin()) {
            return "redirect:";
        }

        return "login";
    }

    /**
     * 登录失败，真正登录的POST请求由Filter完成
     */
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String loginFail(HttpServletRequest request, HttpServletResponse response, Model model) {
        SystemAuthorizingRealm.Principal principal = SecurityUtils.getPrincipal();

        // 如果已经登录，则跳转到管理首页
        if (principal != null) {
            return "redirect:";
        }

        String username = WebUtils.getCleanParam(request, FormAuthenticationFilter.DEFAULT_USERNAME_PARAM);
        boolean rememberMe = WebUtils.isTrue(request, FormAuthenticationFilter.DEFAULT_REMEMBER_ME_PARAM);
        boolean mobile = WebUtils.isTrue(request, FormAuthenticationFilter.DEFAULT_MOBILE_PARAM);
        String exception = (String) request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
        String message = (String) request.getAttribute(FormAuthenticationFilter.DEFAULT_MESSAGE_PARAM);

        if (StringUtils.isBlank(message) || StringUtils.equals(message, "null")) {
            message = "用户或密码错误, 请重试.";
        }

        model.addAttribute(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM, username);
        model.addAttribute(FormAuthenticationFilter.DEFAULT_REMEMBER_ME_PARAM, rememberMe);
        model.addAttribute(FormAuthenticationFilter.DEFAULT_MOBILE_PARAM, mobile);
        model.addAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME, exception);
        model.addAttribute(FormAuthenticationFilter.DEFAULT_MESSAGE_PARAM, message);

        if (logger.isDebugEnabled()) {
            logger.debug("login fail, active session size: {}, message: {}, exception: {}", sessionDAO.getActiveSessions(false).size(), message, exception);
        }

        // 非授权异常，登录失败，验证码加1。
        if (!UnauthorizedException.class.getName().equals(exception)) {
            model.addAttribute("isValidateCodeLogin", UserUtils.isValidateCodeLogin(username, true, false));
        }

        // 验证失败清空验证码
        request.getSession().setAttribute(ValidateCodeServlet.VALIDATE_CODE, IdGen.uuid());

        // 如果是手机登录，则返回JSON字符串
        if (mobile) {
            return renderString(response, model);
        }

        return "login";
    }

    /**
     * 登录成功，进入管理首页
     */
    @RequiresPermissions("user")
    @RequestMapping(value = "")
    public String index(HttpServletRequest request, HttpServletResponse response, Model model) {
        SystemAuthorizingRealm.Principal principal = SecurityUtils.getPrincipal();

        // 登录成功后，验证码计算器清零
        if (principal == null) {
            return "/login";
        }
        UserUtils.isValidateCodeLogin(principal.getLoginName(), false, true);

        if (logger.isDebugEnabled()) {
            logger.debug("show index, active session size: {}", sessionDAO.getActiveSessions(false).size());
        }

        // 如果已登录，再次访问主页，则退出原账号。
        if (TRUE.equals(NOT_ALLOW_REFRESH_INDEX + "")) {
            String logined = CookieUtils.getCookie(request, "LOGINED");
            if (StringUtils.isBlank(logined) || "false".equals(logined)) {
                CookieUtils.setCookie(response, "LOGINED", "true");
            } else if (StringUtils.equals(logined, "true")) {
                SecurityUtils.getSubject().logout();
                return "redirect:" + "/login";
            }
        }

        // 登录成功后，设置用户的菜单
        model.addAttribute("menuList", structure(request.getContextPath()));

        // 如果是手机登录，则返回JSON字符串
        if (principal.isMobileLogin()) {
            if (request.getParameter("login") != null) {
                return renderString(response, principal);
            }
            if (request.getParameter("index") != null) {
                return "index";
            }
            return "redirect:" + "/login";
        }

        return "index";
    }

    @RequestMapping(value = "/dashboard")
    public String dashboard() {
        return "modules/dashboard";
    }

    /**
     * 获取主题方案
     */
    @RequestMapping(value = "/theme/{theme}")
    public String getThemeInCookie(@PathVariable String theme, HttpServletRequest request, HttpServletResponse response) {
        if (StringUtils.isNotBlank(theme)) {
            CookieUtils.setCookie(response, "theme", theme);
        } else {
            theme = CookieUtils.getCookie(request, "theme");
            logger.debug("now theme is {}", theme);
        }
        return "redirect:" + request.getParameter("url");
    }

    /**
     * 系统对外提供单点登录API
     * @param loginParams 登录时需要的参数,通过base64加密后的结果
     *                          {"username":"admin","password":"e10adc3949ba59abbe56e057f20f883e","language":"zh_cn"}
     * @return 登陆后页面(考虑到嵌入其它系统, 以后可能会裁剪index的header和footer)
     */
    @RequestMapping(value = "/apilogin/{loginParams}")
    public String apilogin(@PathVariable String loginParams, HttpServletRequest request) {

        // 登录信息构建示例
        // String username = "admin";
        // String password = PasswordGenerate.md5("123456");
        // String language = "zh_cn";
        // String params = "{\"username\":\"" + username + "\", \"password\":\"" + password + "\", \"language\":\"" + language + "\"}";
        // String href = "server-path" + "/apilogin/" + Base64.encodeBase64String(params.getBytes());

        if (StringUtils.isNoneBlank(loginParams)) {
            String loginInfo = new String(Base64.decodeBase64(loginParams));
            logger.debug("api login info is: {}", loginInfo);
            Map<String, String> val = JsonMapper.getInstance().fromJson(loginInfo,
                    JsonMapper.getInstance().createCollectionType(HashMap.class, String.class, String.class));
            if (val != null) {
                String username = val.get(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM);
                String password = val.get(FormAuthenticationFilter.DEFAULT_PASSWORD_PARAM);
                String language = val.get("language");
                logger.info("api login, --> username is {}, language is {}", username, language);

                Subject subject = SecurityUtils.getSubject();
                // 如果没有授权
                if (!subject.isAuthenticated()) {
                    // 后台模拟登录开始
                    if (StringUtils.isNoneBlank(username) && StringUtils.isNoneBlank(password)) {
                        UsernamePasswordToken token = new UsernamePasswordToken(username, password.toCharArray(),
                                false, request.getRemoteAddr(), null, false);
                        try {
                            subject.login(token);
                        } catch (Exception e) {
                            logger.error("login error: ", e);
                        }
                    }

                }
            }
        }

        return "index";
    }

    /**
     * 系统 rest login api,获取token
     *
     * {"username":"admin","password":""}
     */
    @ResponseBody
    @RequestMapping(value = "/rest/login", method = RequestMethod.POST)
    public Result restLogin(@RequestBody Map<String, String> loginInfo) {

        Result res = new Result(Result.SUCCESS);

        try {
            String username = null;
            String password = null;
            boolean crypt = false;

            if (loginInfo != null) {
                username = loginInfo.get(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM);
                password = loginInfo.get(FormAuthenticationFilter.DEFAULT_PASSWORD_PARAM);
                crypt = Boolean.valueOf(loginInfo.getOrDefault("crypt", String.valueOf(false)));
            }

            Subject subject = SecurityUtils.getSubject();
            // 如果没有授权
            if (!subject.isAuthenticated()) {
                if (username != null && password != null) {

                    UsernamePasswordToken token = new UsernamePasswordToken(username, password.toCharArray(), false,
                            "127.0.0.1", null, false);
                    if (!crypt) {
                        // 未md5加密
                        token.setType(UsernamePasswordToken.LOGIN_TYPE_SIMPLE);
                        token.setPassword(PasswordGenerate.md5(password).toCharArray());
                    }
                    try {

                        subject.login(token);

                        res.setMessage("login success");
                        // success info
                        Map<String, Object> infos = Maps.newHashMap();
                        infos.put("access_token", subject.getSession().getId());
                        infos.put("username", username);
                        infos.put("timeout", subject.getSession().getTimeout());

                        res.setData(infos);

                    } catch (AuthenticationException e) {
                        res.setStatus(Result.ERROR);
                        res.setMessage(e.getMessage());
                        logger.info(" rest/login error: {} ", e);
                    }
                }
            } else {
                res.setMessage("login success");
                // success info
                Map<String, Object> infos = Maps.newHashMap();
                infos.put("access_token", subject.getSession().getId());
                infos.put("username", username);
                infos.put("timeout", subject.getSession().getTimeout());

                res.setData(infos);
            }
        } catch (Exception e) {
            res.setStatus(Result.ERROR);
            res.setMessage(e.getMessage());
            logger.info(" rest/login error: {} ", e);
        }

        return res;
    }

    /**
     * 系统rest 状态保持
     */
    @ResponseBody
    @RequestMapping(value = "/rest/keepalive", method = RequestMethod.POST)
    public Result keepAlive() {
        Result res = new Result(Result.SUCCESS);
        try {
            Subject subject = SecurityUtils.getSubject();
            res.setStatus(Result.SUCCESS);
            res.setMessage("keep alive success");
            res.setData(subject.getPrincipal());
        } catch (Exception e) {
            res.setStatus(Result.ERROR);
            res.setMessage(e.getMessage());
            logger.info(" rest/keepalive error: {} ", e);
        }
        return res;
    }


    /**
     * 系统rest logout api
     */
    @ResponseBody
    @RequestMapping(value = "/rest/logout")
    public Result restLogout() {

        Result res = new Result(Result.SUCCESS);

        try {
            Subject subject = SecurityUtils.getSubject();
            res.setMessage("logout success");
            // success info
            Map<String, Object> infos = Maps.newHashMap();
            infos.put("userinfo", subject.getPrincipal());

            subject.logout();
            res.setStatus(Result.SUCCESS);
            res.setData(infos);

        } catch (Exception e) {
            res.setStatus(Result.ERROR);
            res.setMessage(e.getMessage());
            logger.info(" rest/logout error: {} ", e);
        }
        return res;
    }


    @RequestMapping(value = "/register")
    public String register() {
        return "register";
    }

    @RequestMapping(value = "/forgotpassword")
    public String forgotpassword() {
        return "forgotpassword";
    }

    // ================================================================
    // Getter & Setter
    // ================================================================

    // ================================================================
    // Private Methods
    // ================================================================

    /**
     * 构建当前用户的菜单html
     * @return 菜单html
     */
    private String structure(String contextPath) {

        StringBuilder builder = new StringBuilder("");
        // dashboard 必须存在,首先构建
        builder.append("<li>");
        builder.append("    <a href='").append(contextPath).append("/dashboard' title='Dashboard'>");
        builder.append("        <i class='fa fa-lg fa-fw fa-home txt-color-greenDark'></i> <span class='menu-item-parent'>Dashboard</span>");
        builder.append("    </a>");
        builder.append("</li>");

        // 获取菜单根节点
        Menu root = UserUtils.getRootMenu();
        if (root != null) {
            List<Menu> childList = root.getChildList();
            buildChild(builder, contextPath, childList, true);
        }

        return builder.toString();
    }

    /**
     * 递归构建菜单的html
     * 一般attach有3种效果
     * <span class="badge inbox-badge bg-color-greenLight">v1.10</span>    // 紧邻 (name后)
     * <span class="badge pull-right inbox-badge bg-color-yellow">new</span>  // 最右 (name后)
     * <em>3</em> or <em class="bg-color-pink flash animated">!</em>  //图标上(在i标签内)
     */
    private void buildChild(StringBuilder builder, String contextPath, List<Menu> childList, boolean firstLevel) {
        if (!Collections3.isEmpty(childList)) {
            for (Menu menu : childList) {

                // 设置附加显示效果
                String attach = menu.getAttach();
                String[] vals = new String[]{"", ""};
                if (StringUtils.isNotBlank(attach)) {
                    String[] items = StringUtils.split(attach, "$$$$$");
                    for (String item : items) {
                        if (item.contains("<em")) {
                            vals[0] = item;
                        } else if (item.contains("<span")) {
                            vals[1] = item;
                        }
                    }
                }

                // 构建多级菜单
                if (StringUtils.endsWithIgnoreCase(menu.getTarget(), Menu.MENU_TYPE_MENU)) {
                    builder.append("<li>");
                    builder.append("    <a href='").append(findHref(menu, contextPath)).append("'>");
                    if (firstLevel) {
                        builder.append("<i class='fa-lg fa-fw ").append(menu.getIcon()).append("'>").append(vals[0]).append("</i> ");
                        builder.append("<span class='menu-item-parent'>").append(menu.getName()).append(" </span>").append(vals[1]);
                    } else {
                        builder.append("<i class='fa-fw ").append(menu.getIcon()).append("'></i> ");
                        builder.append(menu.getName()).append(" ").append(vals[1]);
                    }
                    builder.append("</a>");
                    if (hasChild(menu)) {
                        builder.append("<ul>");
                        buildChild(builder, contextPath, menu.getChildList(), false);
                        builder.append("</ul>");
                    }
                    builder.append("</li>");

                } else if (StringUtils.endsWithIgnoreCase(menu.getTarget(), Menu.MENU_TYPE_URL)) {
                    builder.append("<li>");
                    builder.append("<a href='").append(findHref(menu, contextPath)).append("' target='_blank'>");
                    builder.append("<i class='fa-fw ").append(menu.getIcon()).append("'>").append(vals[0]).append("</i> ");
                    builder.append(menu.getName()).append(vals[1]);
                    builder.append("</a>");
                    builder.append("</li>");
                }
            }
        }
    }

    /**
     * 验证menu是否存在非操作子菜单
     * @return 存在
     */
    private boolean hasChild(Menu menu) {
        if (menu != null) {
            List<Menu> childList = menu.getChildList();
            if (!Collections3.isEmpty(childList)) {
                for (Menu child : childList) {
                    if (!StringUtils.endsWithIgnoreCase(child.getTarget(), Menu.MENU_TYPE_OPERATION)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * 根据类型返回真正的链接地址
     */
    private String findHref(Menu menu, String contextPath) {
        if (menu == null) {
            return "#";
        }
        String href = menu.getHref();
        if (StringUtils.isEmpty(href)) {
            return "#";
        } else {
            if (StringUtils.equalsIgnoreCase(menu.getTarget(), Menu.MENU_TYPE_URL)) {
                return href;
            } else if (StringUtils.equalsIgnoreCase(menu.getTarget(), Menu.MENU_TYPE_MENU)) {
                return contextPath + href;
            }
        }
        return "#";
    }

    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    // ================================================================
    // Test Methods
    // ================================================================
}
